
package com.tools.cleanmaster.hijack;

import android.app.ActivityManager;
import android.app.ActivityManager.RunningServiceInfo;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.IInterface;
import android.text.TextUtils;

import com.tools.cleanmaster.utils.Reflections;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Set;

final class FakeActivityManager implements InvocationHandler {

    private static final String SECURITY_UNABLE_FIND_CALLER = "Unable to find app for caller";
    private static final String SECUTITY_UNABLE_START_SERVICE = "Unable to start service";
    private static final String SECURITY_NOT_ALLOWED_START_SERVICE = "Not allowed to start service";
    private static final String SECURITY_ACROSS_USERS_PERMISSION = "android.permission.INTERACT_ACROSS_USERS";

    private static final int ACTIVITY_MANAGER_START_SUCCESS = 0;
    private static final int ACTIVITY_MANAGER_START_PERM_DENAIL = -4;

    // classes we need
    private static final String CLASS_I_ACTIVITY_MANAGER = "android.app.IActivityManager";
    private static final String CLASS_ACTIVITY_MANAGER_NATIVE = "android.app.ActivityManagerNative";
    private static final String CLASS_SINGLETON = "android.util.Singleton";

    // fields we need
    private static final String FIELD_AMN_G_DEFAULT = "gDefault";
    private static final String FIELD_SINGLETON_MINSTANCE = "mInstance";

    // methods we need reflect
    private static final String METHOD_AMN_GET_DEFAULT = "getDefault";

    // methods we need hijack
    private static final String METHOD_GET_CONTENT_PROVIDER = "getContentProvider";
    private static final String METHOD_REF_CONTENT_PROVIDER = "refContentProvider";
    private static final String METHOD_UNSTABLE_PROVIDER_DIED = "unstableProviderDied";
    private static final String METHOD_REMOVE_CONTENT_PROVIDER = "removeContentProvider";
    private static final String METHOD_APP_NOT_RESPONDING_VIA_PROVIDER = "appNotRespondingViaProvider";
    private static final String METHOD_BIND_SERVICE = "bindService";
    private static final String METHOD_PUBLISH_SERVICE = "publishService";
    private static final String METHOD_REGISTER_RECEIVER = "registerReceiver";
    private static final String METHOD_GET_RUNNING_APP_PROCESS = "getRunningAppProcesses";
    private static final String METHOD_GET_SERVICES = "getServices";
    private static final String METHOD_GET_TASKS = "getTasks";
    private static final String METHOD_START_SERVICE = "startService";
    private static final String METHOD_STOP_SERVICE = "stopService";
    private static final String METHOD_START_ACTIVITY = "startActivity";
    private static final String METHOD_FINISH_ACTIVITY = "finishActivity";
    private static final String METHOD_BROADCAST_INTENT = "broadcastIntent";
    private static final String METHOD_CHECK_PERMISSION = "checkPermission";
    private static final String METHOD_GET_INTENT_SENDER = "getIntentSender";
    private static final String METHOD_KILL_BACKGROUND_PROGRESSES = "killBackgroundProcesses";

    private static final Set<String> sInvokeMethods = new LinkedHashSet<String>();

    static {
        sInvokeMethods.add(METHOD_GET_CONTENT_PROVIDER);
        sInvokeMethods.add(METHOD_REF_CONTENT_PROVIDER);
        sInvokeMethods.add(METHOD_UNSTABLE_PROVIDER_DIED);
        sInvokeMethods.add(METHOD_REMOVE_CONTENT_PROVIDER);
        sInvokeMethods.add(METHOD_APP_NOT_RESPONDING_VIA_PROVIDER);
        sInvokeMethods.add(METHOD_BIND_SERVICE);
        sInvokeMethods.add(METHOD_PUBLISH_SERVICE);
        sInvokeMethods.add(METHOD_REGISTER_RECEIVER);
        sInvokeMethods.add(METHOD_GET_RUNNING_APP_PROCESS);
        sInvokeMethods.add(METHOD_GET_SERVICES);
        sInvokeMethods.add(METHOD_GET_TASKS);
        sInvokeMethods.add(METHOD_START_SERVICE);
        sInvokeMethods.add(METHOD_STOP_SERVICE);
        sInvokeMethods.add(METHOD_START_ACTIVITY);
        sInvokeMethods.add(METHOD_FINISH_ACTIVITY);
        sInvokeMethods.add(METHOD_BROADCAST_INTENT);
        sInvokeMethods.add(METHOD_CHECK_PERMISSION);
        sInvokeMethods.add(METHOD_GET_INTENT_SENDER);
        sInvokeMethods.add(METHOD_KILL_BACKGROUND_PROGRESSES);
    }

    private static Object sInstance = null;

    private static synchronized Object createProxyInstance(Context ctx, IInterface target) {
        ClassLoader targetLoader = target.getClass().getClassLoader();
        Class<?> targetClass = target.getClass();
        FakeActivityManager amProxy = new FakeActivityManager(ctx, target);
        Class<?>[] interfaces = Reflections.collectInterfaces(targetClass);
        return Proxy.newProxyInstance(targetLoader, interfaces, amProxy);
    }

    private static synchronized Object peekProxyInstance() {
        return sInstance;
    }

    public static synchronized boolean hijackActivityManager(Context ctx) {
        Object legacyProxy = peekProxyInstance();
        if (legacyProxy != null) {
            return true;
        }

        Class<?> amnClass = Reflections.loadClassNoThrow(CLASS_ACTIVITY_MANAGER_NATIVE);
        Method method = Reflections.findMethodNoThrow(amnClass, METHOD_AMN_GET_DEFAULT);
        Object target = Reflections.invokeMethodNoThrow(method, null);
        if (target == null || !(target instanceof IInterface)) {
            return false;
        }

        Object proxy = createProxyInstance(ctx, (IInterface) target);
        if (proxy == null) {
            return false;
        }

        Class<?> iamClass = Reflections.loadClassNoThrow(CLASS_I_ACTIVITY_MANAGER);
        if (iamClass == null || !iamClass.isInstance(proxy)) {
            return false;
        }

        Field gDefaultField = Reflections.findFieldNoThrow(amnClass, FIELD_AMN_G_DEFAULT);
        Object gDefault = Reflections.getValueNoThrow(gDefaultField, null);
        if (gDefault == null) {
            return false;
        }

        boolean success = false;

        if (iamClass != null && iamClass.isAssignableFrom(gDefault.getClass())) {
            success = Reflections.setValueNoThrow(gDefaultField, null, proxy);
        } else {
            Class<?> singletonClass = Reflections.loadClassNoThrow(CLASS_SINGLETON);
            Field instanceField = Reflections.findFieldNoThrow(singletonClass,
                    FIELD_SINGLETON_MINSTANCE);
            success = Reflections.setValueNoThrow(instanceField, gDefault, proxy);
        }

        if (success) {
            sInstance = proxy;
        }
        return success;
    }

    private final IInterface mTarget;
    private final Context mAppCtx;

    private FakeActivityManager(Context ctx, IInterface target) {
        mAppCtx = ctx.getApplicationContext();
        mTarget = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        if (!sInvokeMethods.contains(methodName)) {
            return Reflections.invokeMethod(method, mTarget, args);
        } else if (TextUtils.equals(methodName, METHOD_BIND_SERVICE)) {
            return handleHijackBindService(proxy, method, args);
        } else if (TextUtils.equals(methodName, METHOD_PUBLISH_SERVICE)) {
            return handleHijackPublishService(proxy, method, args);
        } else if (TextUtils.equals(methodName, METHOD_REGISTER_RECEIVER)) {
            return handleHijackRegisterReceiver(proxy, method, args);
        } else if (TextUtils.equals(methodName, METHOD_GET_RUNNING_APP_PROCESS)) {
            return handleHijackGetRunningAppProcesses(proxy, method, args);
        } else if (TextUtils.equals(methodName, METHOD_GET_SERVICES)) {
            return handleHijackGetServices(proxy, method, args);
        } else if (TextUtils.equals(methodName, METHOD_GET_TASKS)) {
            return handleHijackGetTasks(proxy, method, args);
        } else if (TextUtils.equals(methodName, METHOD_START_SERVICE)) {
            return handleHijackStartService(proxy, method, args);
        } else if (TextUtils.equals(methodName, METHOD_START_ACTIVITY)) {
            return handleHijackStartActivity(proxy, method, args);
        } else if (TextUtils.equals(methodName, METHOD_STOP_SERVICE)) {
            return handleHijackStopService(proxy, method, args);
        } else if (TextUtils.equals(methodName, METHOD_FINISH_ACTIVITY)) {
            return handleHijackFinishActivity(proxy, method, args);
        } else if (TextUtils.equals(methodName, METHOD_BROADCAST_INTENT)) {
            return handleHijackBroadcastIntent(proxy, method, args);
        } else if (TextUtils.equals(methodName, METHOD_CHECK_PERMISSION)) {
            return handleHijackCheckPermission(proxy, method, args);
        } else if (TextUtils.equals(methodName, METHOD_GET_INTENT_SENDER)) {
            return handleHijackGetIntentSender(proxy, method, args);
        } else if (TextUtils.equals(methodName, METHOD_KILL_BACKGROUND_PROGRESSES)) {
            return handleHijackKillBackgroundProgresses(proxy, method, args);
        } else {
            return Reflections.invokeMethod(method, mTarget, args);
        }
    }

    // LG android 7.0 killBackgroundProgress throw NullPointException
    private Object handleHijackKillBackgroundProgresses(Object proxy, Method method, Object[] args)
            throws Throwable {
        Object result = null;
        try {
            result = Reflections.invokeMethod(method, mTarget, args);
        } catch (NullPointerException e) {
            HijackReporter.reportException(e);
        }
        return result;
    }

    // android4.2
    // public int stopService(IApplicationThread caller, Intent service,
    // String resolvedType, int userId) throws RemoteException;

    // android4.0.1
    // public int stopService(IApplicationThread caller, Intent service,
    // String resolvedType) throws RemoteException;

    // android2.3.2
    // public int stopService(IApplicationThread caller, Intent service,
    // String resolvedType, int userId) throws RemoteException;
    private Object handleHijackStopService(Object proxy, Method method, Object[] args)
            throws Throwable {
        Object result = null;
        try {
            result = Reflections.invokeMethod(method, mTarget, args);
        } catch (SecurityException e) {
            handleUnableFindCallerException(e);
        }

        // result等于表示stopService失败，但不在上层抛出异常
        if (result == null) {
            result = 0;
        }

        return result;
    }

    // android5.0.0
    // public boolean finishActivity(IBinder token, int code, Intent data, boolean finishTask)
    // throws RemoteException;

    // android2.3.2
    // public boolean finishActivity(IBinder token, int code, Intent data)
    // throws RemoteException;
    private Object handleHijackFinishActivity(Object proxy, Method method, Object[] args)
            throws Throwable {
        Object result = null;
        try {
            result = Reflections.invokeMethod(method, mTarget, args);
        } catch (NullPointerException e) {
            HijackReporter.reportException(e);
        }
        if (result == null) {
            result = false;
        }
        return result;
    }

    // android4.2
    // public IIntentSender getIntentSender(int type,
    // String packageName, IBinder token, String resultWho,
    // int requestCode, Intent[] intents, String[] resolvedTypes,
    // int flags, Bundle options, int userId) throws RemoteException;

    // android4.1
    // public IIntentSender getIntentSender(int type,
    // String packageName, IBinder token, String resultWho,
    // int requestCode, Intent[] intents, String[] resolvedTypes,
    // int flags, Bundle options) throws RemoteException;

    // android4.0.1
    // public IIntentSender getIntentSender(int type,
    // String packageName, IBinder token, String resultWho,
    // int requestCode, Intent[] intents, String[] resolvedTypes,
    // int flags) throws RemoteException;

    // android2.3.2
    // public IIntentSender getIntentSender(int type,
    // String packageName, IBinder token, String resultWho,
    // int requestCode, Intent intent, String resolvedType, int flags) throws RemoteException;
    private Object handleHijackGetIntentSender(Object proxy, Method method, Object[] args)
            throws Throwable {

        Object arg8 = (args != null && args.length >= 8) ? args[7] : null;
        int flags = (arg8 instanceof Integer) ? ((Integer) arg8).intValue() : 0;

        // 判断是否带有FLAG_CANCEL_CURRENT
        // 如果有就替换为FLAG_UPDATE_CURRENT
        if ((flags & PendingIntent.FLAG_CANCEL_CURRENT) != 0) {
            flags &= ~PendingIntent.FLAG_CANCEL_CURRENT;
            flags |= PendingIntent.FLAG_UPDATE_CURRENT;
            args[7] = flags;
        }

        return Reflections.invokeMethod(method, mTarget, args);
    }

    private Object handleHijackCheckPermission(Object proxy, Method method, Object[] args)
            throws Throwable {
        Object result = Reflections.invokeMethod(method, mTarget, args);

        if (result == null) {
            result = PackageManager.PERMISSION_DENIED;
        }
        return result;
    }

    // android4.3
    // public int broadcastIntent(IApplicationThread caller, Intent intent,
    // String resolvedType, IIntentReceiver resultTo, int resultCode,
    // String resultData, Bundle map, String requiredPermission,
    // int appOp, boolean serialized, boolean sticky, int userId) throws RemoteException;

    // android4.1.1
    // public int broadcastIntent(IApplicationThread caller, Intent intent,
    // String resolvedType, IIntentReceiver resultTo, int resultCode,
    // String resultData, Bundle map, String requiredPermission,
    // boolean serialized, boolean sticky, int userId) throws RemoteException;

    // android2.3.2
    // public int broadcastIntent(IApplicationThread caller, Intent intent,
    // String resolvedType, IIntentReceiver resultTo, int resultCode,
    // String resultData, Bundle map, String requiredPermission,
    // boolean serialized, boolean sticky) throws RemoteException;
    private Object handleHijackBroadcastIntent(Object proxy, Method method, Object[] args)
            throws Throwable {
        Object result = null;
        try {
            result = Reflections.invokeMethod(method, mTarget, args);
        } catch (SecurityException e) {
            handleBroadCastIntentSecurityException(e);
        } catch (NullPointerException e) {
            e.printStackTrace();
        }

        return (result instanceof Integer) ? result : 0;
    }

    private void handleBroadCastIntentSecurityException(SecurityException e) {
        String message = e.getMessage();
        boolean myPackage = TextUtils.indexOf(message, mAppCtx.getPackageName()) >= 0;
        //boolean batterPkg = TextUtils.indexOf(message, CleanConstant.DX_BATTERY_PACKAGENAME) >= 0;
        boolean acrossPerm = TextUtils.indexOf(message, SECURITY_ACROSS_USERS_PERMISSION) >= 0;
        if ((myPackage /*|| batterPkg*/) && acrossPerm) {
            try {
                HijackReporter.reportException(e);
            } catch (Throwable t) {
                t.printStackTrace();
            }
        } else {
            throw e;
        }
    }

    // android5.0.0
    // public int startActivity(IApplicationThread caller, String callingPackage, Intent intent,
    // String resolvedType, IBinder resultTo, String resultWho, int requestCode, int flags,
    // ProfilerInfo profilerInfo, Bundle options) throws RemoteException;

    // android4.1.1
    // public int startActivity(IApplicationThread caller,
    // Intent intent, String resolvedType, IBinder resultTo, String resultWho,
    // int requestCode, int flags, String profileFile,
    // ParcelFileDescriptor profileFd, Bundle options) throws RemoteException;

    // android2.3.2
    // public int startActivity(IApplicationThread caller,
    // Intent intent, String resolvedType, Uri[] grantedUriPermissions,
    // int grantedMode, IBinder resultTo, String resultWho, int requestCode,
    // boolean onlyIfNeeded, boolean debug) throws RemoteException;
    private Object handleHijackStartActivity(Object proxy, Method method, Object[] args)
            throws Throwable {

        Object result = Reflections.invokeMethod(method, mTarget, args);

        // 这个场景不存在，出于代码严谨考虑加上这一行
        if (!(result instanceof Integer)) {
            return ACTIVITY_MANAGER_START_SUCCESS;
        }

        int resultCode = ((Integer) result).intValue();
        return checkStartActivityResultCode(resultCode, args);
    }

    private int checkStartActivityResultCode(int resultCode, Object[] args) {
        if (resultCode != ACTIVITY_MANAGER_START_PERM_DENAIL) {
            return resultCode;
        }

        int intentIndex = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? 2 : 1;
        if (args == null || args.length <= intentIndex || !(args[intentIndex] instanceof Intent)) {
            return resultCode;
        }

        // 只处理自己应用的组件
        String intent = args[intentIndex].toString();
        if (TextUtils.indexOf(intent, mAppCtx.getPackageName()) >= 0) {
            return ACTIVITY_MANAGER_START_SUCCESS;
        } else {
            return resultCode;
        }
    }

    // android2.3.2
    // public ComponentName startService(IApplicationThread caller, Intent service,
    // String resolvedType) throws RemoteException;
    private Object handleHijackStartService(Object proxy, Method method, Object[] args)
            throws Throwable {

        Object result = null;
        try {
            result = Reflections.invokeMethod(method, mTarget, args);
        } catch (SecurityException e) {
            handleUnableStartServiceException(e);
        }

        return result;
    }

    private void handleUnableStartServiceException(SecurityException e)
            throws SecurityException {
        String message = e.getMessage();
        boolean unableStart = TextUtils.indexOf(message, SECUTITY_UNABLE_START_SERVICE) >= 0;
        boolean unableFindCaller = TextUtils.indexOf(message, SECURITY_UNABLE_FIND_CALLER) >= 0;
        boolean notAllowed = TextUtils.indexOf(message, SECURITY_NOT_ALLOWED_START_SERVICE) >= 0;
        boolean myPackage = TextUtils.indexOf(message, mAppCtx.getPackageName()) >= 0;
        //boolean batteryPkg = TextUtils.indexOf(message, CleanConstant.DX_BATTERY_PACKAGENAME) >= 0;
        if ((unableStart || notAllowed || unableFindCaller) && (myPackage /*|| batteryPkg*/)) {
            try {
                HijackReporter.reportException(e);
            } catch (Throwable t) {
                t.printStackTrace();
            }
        } else {
            throw e;
        }
    }

    // 修复获取task时出现的空指针异常
    private Object handleHijackGetTasks(Object proxy, Method method, Object[] args)
            throws Throwable {
        Object result = null;
        try {
            result = Reflections.invokeMethod(method, mTarget, args);
        } catch (NullPointerException e) {
            e.printStackTrace();
        }

        if (result == null) {
            result = new LinkedList<RunningTaskInfo>();
        }
        return result;
    }

    // 修复获取service时出现的空指针异常
    private Object handleHijackGetServices(Object proxy, Method method, Object[] args)
            throws Throwable {

        Object result = null;
        try {
            result = Reflections.invokeMethod(method, mTarget, args);
        } catch (NullPointerException e) {
            e.printStackTrace();
        }

        if (result == null) {
            result = new LinkedList<RunningServiceInfo>();
        }
        // 在返回的List为null时，返回一个空的List，防止程序崩溃
        return result;
    }

    // 修复在android5.0及以上设备获取进程信息时，返回list为null导致应用崩溃的问题
    private Object handleHijackGetRunningAppProcesses(Object proxy, Method method, Object[] args)
            throws Throwable {
        Object result = Reflections.invokeMethod(method, mTarget, args);
        if (result == null) {
            result = new LinkedList<ActivityManager.RunningAppProcessInfo>();
        }
        return result;
    }


    // public int bindService(IApplicationThread caller, IBinder token,
    // Intent service, String resolvedType,
    // IServiceConnection connection, int flags, int userId) throws RemoteException;
    private Object handleHijackBindService(Object proxy, Method method, Object[] args)
            throws Throwable {
        Object result = null;
        try {
            result = Reflections.invokeMethod(method, mTarget, args);
        } catch (SecurityException e) {
            handleUnableFindCallerException(e);
        } catch (NullPointerException e) {
            e.printStackTrace();
        }

        if (result == null) {
            return 0;
        } else {
            return ((Integer) result) < 0 ? 0 : result;
        }
    }

    // public void publishService(IBinder token,
    // Intent intent, IBinder service) throws RemoteException;
    private Object handleHijackPublishService(Object proxy, Method method, Object[] args)
            throws Throwable {
        Object result = null;

        try {
            result = Reflections.invokeMethod(method, mTarget, args);
        } catch (SecurityException e) {
            handleUnableFindCallerException(e);
        } catch (NullPointerException e) {
            e.printStackTrace();
        }

        return result;
    }

    // public Intent registerReceiver(IApplicationThread caller, String callerPackage,
    // IIntentReceiver receiver, IntentFilter filter,
    // String requiredPermission, int userId) throws RemoteException;
    private Object handleHijackRegisterReceiver(Object proxy, Method method, Object[] args)
            throws Throwable {
        Object result = null;
        try {
            result = Reflections.invokeMethod(method, mTarget, args);
        } catch (SecurityException e) {
            handleUnableFindCallerException(e);
        } catch (NullPointerException e) {
            e.printStackTrace();
        }
        return result;
    }

    private void handleUnableFindCallerException(SecurityException e)
            throws SecurityException {
        String message = e.getMessage();
        if (TextUtils.indexOf(message, SECURITY_UNABLE_FIND_CALLER) >= 0) {
            // 已确认是覆盖安装的问题，关闭异常上报
            e.printStackTrace();
        } else if (TextUtils.indexOf(message, SECURITY_ACROSS_USERS_PERMISSION) >= 0) {
            boolean myPackage = TextUtils.indexOf(message, mAppCtx.getPackageName()) >= 0;
            //boolean batteryPkg = TextUtils.indexOf(message, CleanConstant.DX_BATTERY_PACKAGENAME) >= 0;
            if (myPackage /*|| batteryPkg*/) {
                HijackReporter.reportException(e);
            } else {
                throw e;
            }
        } else {
            throw e;
        }
    }
}
